package net.borlis.sample.service.repositories.common;

import net.borlis.sample.service.db.em.Simple;
import net.borlis.sample.service.entities.common.BaseEntityInterface;
import net.borlis.sample.service.repositories.cache.Cache;
import net.borlis.sample.service.repositories.cache.ICacheConfig;

import javax.annotation.PostConstruct;
import javax.enterprise.context.RequestScoped;
import javax.inject.Inject;
import javax.persistence.EntityManager;
import javax.persistence.NoResultException;
import javax.persistence.Query;
import javax.persistence.TypedQuery;
import javax.persistence.criteria.*;
import java.util.List;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * Created by alexander.borlis on 15.07.2014.
 */
@RequestScoped
public abstract class AbstractDynamixRepository<T extends BaseEntityInterface> implements IAbstractDynamixRepository<T> {

    private static final String CACHE_PARAM = "org.hibernate.cacheable";

    @Inject
    private Logger logger;

    @Inject @Simple
    private EntityManager em;

    @Inject
    @Cache(cacheState = false)
    private ICacheConfig cacheConfig;

    /**
     * possible operations on CriteriaQuery {@link javax.persistence.criteria.CriteriaQuery}
     */
    public enum Operation {
        select,
        delete,
        update
    }

    private CriteriaBuilder builder;
    private CriteriaQuery<T> selectCriteria;
    private CriteriaUpdate<T> updateCriteria;
    private CriteriaDelete<T> deleteCriteria;
    private Root<T> from;
    private TypedQuery<T> typedQuery;
    private Query query;

    @PostConstruct
    public void initQueryTools() {
        builder = em.getCriteriaBuilder();
    }

    public EntityManager getEm() {
        return em;
    }

    public void beginTransaction() {
        getEm().getTransaction().begin();
    }

    public void commitTransaction() {
        getEm().getTransaction().commit();
    }

    public void rollbackTransaction() {
        getEm().getTransaction().rollback();
    }

    public abstract Class<T> getEntityClass();

    protected T findBy(Predicate... restrictions) {
        selectCriteria.where(restrictions);
        typedQuery = getEm().createQuery(selectCriteria);
        typedQuery.setHint(CACHE_PARAM, cacheConfig.getEnabled());
        return typedQuery.getSingleResult();
    }

    protected List<T> findAllBy(Predicate... restrictions) {
        selectCriteria.where(restrictions);
        typedQuery = getEm().createQuery(selectCriteria);
        typedQuery.setHint(CACHE_PARAM, cacheConfig.getEnabled());
        return typedQuery.getResultList();
    }

    protected List<T> findAllBy(Predicate[] restrictions, Order[] orders) {
        selectCriteria.where(restrictions);
        selectCriteria.orderBy(orders);
        typedQuery = getEm().createQuery(selectCriteria);
        typedQuery.setHint(CACHE_PARAM, cacheConfig.getEnabled());
        return typedQuery.getResultList();
    }

    protected List<T> findAllBy(Predicate[] predicates, Integer from, Integer items) {
            selectCriteria.where(predicates);
            typedQuery = getEm().createQuery(selectCriteria);
            typedQuery.setHint(CACHE_PARAM, cacheConfig.getEnabled());
            if (from != null) {
                typedQuery.setFirstResult(from);
            }
            if (items != null) {
                typedQuery.setMaxResults(items);
            }
            return typedQuery.getResultList();
    }

    public List<T> findAllBy(Set<Predicate> restrictions) {
        return findAllBy(restrictions.toArray(new Predicate[restrictions.size()]));
    }

    protected Integer countByPredicates(Predicate[] predicates) {
            builder = getEm().getCriteriaBuilder();
            selectCriteria = builder.createQuery(getEntityClass());
            selectCriteria.where(predicates);
            from = selectCriteria.from(getEntityClass());
            selectCriteria.select(from);
            typedQuery = getEm().createQuery(selectCriteria);
            typedQuery.setHint(CACHE_PARAM, cacheConfig.getEnabled());
            return typedQuery.getResultList().size();
    }

    @Override
    public T create(T t) {
        getEm().persist(t);
        logger.log(Level.FINE, "The " + t + " created.");
        return t;
    }

    @Override
    public T find(Long id) {
        T t = getEm().find(getEntityClass(), id);
        if (t == null) {
            throw new NoResultException("There is no " + getEntityClass().getSimpleName() + " with id = " + id);
        }
        return t;
    }

    @Override
    public T update(T t) {
        return this.em.merge(t);
    }

    @Override
    public void delete(T t) {
        T tUp = update(t);
        getEm().remove(tUp);
    }

    public int deleteBy(Predicate... restrictions) {
        deleteCriteria.where(restrictions);
        query = getEm().createQuery(deleteCriteria);
        typedQuery.setHint(CACHE_PARAM, cacheConfig.getEnabled());
        return query.executeUpdate();
    }

    @Override
    public void delete(Long id) {
        T t = find(id);
        delete(t);
    }

    @Override
    public void deleteAll() {
        getFrom(Operation.delete);
        query = getEm().createQuery(deleteCriteria);
        query.executeUpdate();
    }

    @Override
    public void deleteWithoutUpdate(T t) {
        getEm().remove(t);
    }

    @Override
    public void deleteWithoutUpdate(Long id) {
        T t = find(id);
        deleteWithoutUpdate(t);
    }

    @Override
    public List<T> findAll() {
        //todo:: rework in a generalized way
        builder = getEm().getCriteriaBuilder();
        selectCriteria = builder.createQuery(getEntityClass());
        from = selectCriteria.from(getEntityClass());
        selectCriteria.select(from);
        typedQuery = em.createQuery(selectCriteria);
        typedQuery.setHint(CACHE_PARAM, cacheConfig.getEnabled());
        return typedQuery.getResultList();
    }

//    @PreDestroy
//    public void destroy() {
//        if (getEm().isOpen()) {
//            getEm().close();
//        }
//    }

    public CriteriaBuilder getBuilder() {
        return builder;
    }

    public CriteriaQuery<T> getCriteria() {
        return selectCriteria;
    }

    /**
     * depends on operation type method initialize CriteriaQuery, CriteriaUpdate or CriteriaDelete
     *
     * @param {@link Operation} operation to be initialized
     */
    public Root<T> getFrom(Operation operation) {
        switch (operation) {
            case select: {
                if (selectCriteria == null) {
                    selectCriteria = builder.createQuery(getEntityClass());
                    from = selectCriteria.from(getEntityClass());
                }
                return from;
            }
            case delete: {
                if (deleteCriteria == null) {
                    deleteCriteria = builder.createCriteriaDelete(getEntityClass());
                    from = deleteCriteria.from(getEntityClass());
                }
                return from;
            }
            case update: {
                if (updateCriteria == null) {
                    updateCriteria = builder.createCriteriaUpdate(getEntityClass());
                    from = updateCriteria.from(getEntityClass());
                }
                return from;
            }
            default: {
                selectCriteria = builder.createQuery(getEntityClass());
                from = selectCriteria.from(getEntityClass());
                return from;
            }
        }
    }

    public TypedQuery<T> getTypedQuery() {
        return typedQuery;
    }



}
